package xin.ctkqiang.controller;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import xin.ctkqiang.dto.Exploit;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.text.StringEscapeUtils;

/**
 * ExploitDbController 类
 * 负责从 Exploit-DB 网站爬取漏洞信息并进行数据处理
 * 继承自 DatabaseController 以复用数据库操作功能
 */
public class ExploitDbController extends DatabaseController {
    /** Exploit-DB 网站的基础URL */
    protected final static String URL = "https://www.exploit-db.com/";

    /** 调试模式开关，从配置文件中读取 */
    private static final boolean debug = xin.ctkqiang.config.Configuration.DEBUG;

    /** 每次爬取的数据条数 */
    public static final int PAGE_SIZE = 50;

    /** HTTP客户端，用于发送网络请求 */
    private HttpClient Client = HttpClient.newHttpClient();

    /** JSON解析器，用于处理API响应数据 */
    private ObjectMapper Mapper = new ObjectMapper();

    private static final List<String> BLOCK_KEYWORDS = Arrays.asList(
            "firewall", "access denied", "blocked", "forbidden", "connection refused");

    /**
     * 获取数据库中所有的漏洞信息
     * 
     * @return 漏洞信息列表
     */
    @Override
    public List<Exploit> GetAllExploits() {
        return super.GetAllExploits();
    }

    /**
     * 创建数据表（如果不存在）
     * 用于初始化数据库结构
     */
    @Override
    public void CreateTableIfNotExists() {
        super.CreateTableIfNotExists();
    }

    /**
     * 将漏洞信息导出为CSV文件
     * 
     * @param exploits 要导出的漏洞信息列表
     * @return 导出状态码
     */
    @Override
    public int ExportToCSV(List<Exploit> exploits) {
        return super.ExportToCSV(exploits);
    }

    /**
     * 创建新的漏洞记录（未实现）
     */
    @SuppressWarnings("unused")
    private int create(String id, String description, String date, String author, String type, String platform) {
        return 0;
    }

    /**
     * 爬取Exploit-DB网站的漏洞信息
     * 
     * @param pageSize  每页获取的记录数量
     * @param isExport  是否导出数据
     * @param extension 导出文件的扩展名（支持：csv, json, sql）
     */
    public void Crawl(int pageSize, boolean isExport, String extension) {
        // 构建API请求URL，包含分页参数
        String url = String.format(ExploitDbController.URL + "search?draw=1&start=%d&length=%d", 0, pageSize);

        // 初始化状态码和结果列表
        int Status = 0;
        List<Exploit> exploits = new ArrayList<>();

        try {
            // 构建HTTP请求，设置必要的请求头
            HttpRequest Request = HttpRequest.newBuilder()
                    .uri(URI.create(url))
                    .header("User-Agent",
                            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36")
                    .header("Accept", "application/json, text/javascript, */*; q=0.01")
                    .header("Accept-Language", "en-US,en;q=0.9")
                    .header("Referer", ExploitDbController.URL)
                    .header("X-Requested-With", "XMLHttpRequest")
                    .GET()
                    .build();

            // 发送HTTP请求并获取响应
            HttpResponse<String> Response = Client.send(Request, HttpResponse.BodyHandlers.ofString());

            // 调试模式下打印响应预览
            if (debug) {
                System.out.println("响应内容预览：" + Response.body().substring(0, 500));
            }

            // 解析JSON响应
            JsonNode Root = this.Mapper.readTree(Response.body());
            JsonNode Data = Root.get("data");

            // 处理数据数组
            if (Data.isArray()) {
                // 确保数据有效性
                assert Data.isArray() && Data.size() > 0 : "Data 不是一个数组或大小为0";

                // 输出进度信息
                System.out.println("正在爬取 Exploit-DB...");
                System.out.println("共找到 " + Data.size() + " 条记录。");
                System.out.println("正在写入数据库...");

                // 调试模式下打印完整数据
                if (debug) {
                    System.out.println(Data);
                }

                // 遍历每条漏洞记录并解析
                for (JsonNode Node : Data) {
                    Exploit exploit = new Exploit();

                    // 设置漏洞ID
                    exploit.setId(Node.get("id").asText());

                    // 获取各字段节点
                    JsonNode descriptionNode = Node.get("description");
                    JsonNode authorNode = Node.get("author");
                    JsonNode typeNode = Node.get("type");
                    JsonNode platformNode = Node.get("platform");

                    // 解析描述信息（数组格式）
                    if (descriptionNode != null && descriptionNode.isArray() && descriptionNode.size() > 1) {
                        exploit.setDescription(descriptionNode.get(1).asText());
                    } else {
                        exploit.setDescription("");
                    }

                    // 解析发布日期
                    if (Node.has("date_published")) {
                        exploit.setDate(Node.get("date_published").asText());
                    } else {
                        exploit.setDate("");
                    }

                    // 解析作者信息
                    if (authorNode != null && authorNode.has("name")) {
                        exploit.setAuthor(authorNode.get("name").asText());
                    } else {
                        exploit.setAuthor("");
                    }

                    // 解析漏洞类型
                    if (typeNode != null && typeNode.has("name")) {
                        exploit.setType(typeNode.get("name").asText());
                    } else {
                        exploit.setType("");
                    }

                    // 解析影响平台
                    if (platformNode != null && platformNode.has("platform")) {
                        exploit.setPlatform(platformNode.get("platform").asText());
                    } else {
                        exploit.setPlatform("");
                    }

                    // 添加到结果列表
                    exploits.add(exploit);
                }
                // 将爬取的数据保存到数据库
                Status = super.AddExploit(exploits);

                // 如果数据保存成功
                if (Status != (-1)) {
                    // 在控制台打印数据表格
                    ExploitDbController.PrintExploitTable(exploits);

                    // 如果需要导出数据
                    if (isExport) {
                        int status;

                        // 根据指定的文件格式导出数据
                        switch (extension) {
                            case "sql":
                                status = super.ExportToSQL(exploits);
                                break;
                            case "json":
                                status = super.ExportToJSON(exploits);
                                break;
                            case "yaml":
                                status = super.ExportToYAML(exploits);
                                break;
                            case "csv":
                            default:
                                status = super.ExportToCSV(exploits);
                                break;
                        }

                        // 显示导出状态
                        this.ExportStatus(status, extension);
                    }
                }
            }

        } catch (IOException | InterruptedException e) {
            String msg = e.getMessage();

            if (msg != null && BLOCK_KEYWORDS.stream().anyMatch(k -> msg.toLowerCase().contains(k))) {
                System.err.println("爬取 Exploit-DB : ⚠️ 请求被阻止，可能遭遇防火墙、访问限制或代理问题，请检查网络配置。");
            } else {
                System.err.println("爬取 Exploit-DB 时出错：" + msg);
            }
        }

        // 显示最终的爬取结果
        if (Status != 0) {
            System.out.println("成功爬取 Exploit-DB！\n");
        }

        return;
    }

    /**
     * 添加漏洞信息到数据库
     * 
     * @param exploit_list 要添加的漏洞信息列表
     * @return 操作状态码
     */
    @Override
    public int AddExploit(List<Exploit> exploit_list) {
        return super.AddExploit(exploit_list);
    }

    @Override
    public int ExportToYAML(List<Exploit> exploits) {
        return super.ExportToYAML(exploits);
    }

    /**
     * 将漏洞信息导出为JSON格式
     * 
     * @param exploits 要导出的漏洞信息列表
     * @return 导出状态码
     */
    @Override
    public int ExportToJSON(List<Exploit> exploits) {
        return super.ExportToJSON(exploits);
    }

    /**
     * 将漏洞信息导出为SQL格式
     * 
     * @param exploits 要导出的漏洞信息列表
     * @return 导出状态码
     */
    @Override
    public int ExportToSQL(List<Exploit> exploits) {
        return super.ExportToSQL(exploits);
    }

    /**
     * 在控制台以表格形式打印漏洞信息
     * 
     * @param exploits 要打印的漏洞信息列表
     */
    public static void PrintExploitTable(List<Exploit> exploits) {
        // 定义表格格式
        String format = "| %-4s | %-40s | %-10s | %-15s | %-10s | %-10s |%n";

        // 打印表头
        System.out.println(
                "+------+------------------------------------------+------------+-----------------+------------+------------+");
        System.out.printf(format, "编号", "描述", "日期", "作者", "类型", "平台");
        System.out.println(
                "+------+------------------------------------------+------------+-----------------+------------+------------+");

        // 打印表格内容
        int index = 1;
        for (Exploit e : exploits) {
            // 处理并格式化各字段
            String desc = truncateAndPad(StringEscapeUtils.unescapeHtml4(e.getDescription()).trim(), 40);
            String date = padRight(e.getDate().trim(), 10);
            String author = truncateAndPad(e.getAuthor().trim(), 15);
            String type = padRight(e.getType().trim(), 10);
            String platform = padRight(e.getPlatform().trim(), 10);

            // 打印一行数据
            System.out.printf(format, index++, desc, date, author, type, platform);
        }

        // 打印表格底部
        System.out.println(
                "+------+------------------------------------------+------------+-----------------+------------+------------+");
    }

    /**
     * 截断并填充字符串到指定长度
     * 
     * @param str       原始字符串
     * @param maxLength 目标长度
     * @return 处理后的字符串
     */
    private static String truncateAndPad(String str, int maxLength) {
        str = str.trim();
        // 如果字符串过长，截断并添加省略号
        if (str.length() > maxLength) {
            str = str.substring(0, maxLength - 3) + "...";
        }

        return padRight(str, maxLength);
    }

    /**
     * 右填充字符串到指定长度
     * 
     * @param str    原始字符串
     * @param length 目标长度
     * @return 填充后的字符串
     */
    private static String padRight(String str, int length) {
        StringBuilder sb = new StringBuilder(str);
        // 用空格填充到指定长度
        while (sb.length() < length) {
            sb.append(' ');
        }
        return sb.toString();
    }

    /**
     * 显示导出操作的状态
     * 
     * @param status 导出状态码
     * @param ext    文件扩展名
     */
    private void ExportStatus(int status, String ext) {
        // ANSI转义序列：用于控制台颜色
        String green = "\u001B[32m";
        String red = "\u001B[31m";
        String reset = "\u001B[0m";

        // 根据状态码生成对应的提示信息
        String msg = status != 0
                ? green + "✅ 成功导出文件为 ." + ext + reset
                : red + "❌ 导出失败：请检查写入权限、路径或文件是否被占用（." + ext + "）" + reset;

        System.out.println(msg);
    }

}
